package com.liuya.safe.client.service.impl;

import java.io.ByteArrayInputStream;
import java.io.FileReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.liuya.common.exception.EntityExistException;
import com.liuya.common.StringUtil;
import com.liuya.db.util.DBUtil;
import com.liuya.safe.client.UserMetadataParser;
import com.liuya.safe.client.dao.AppUserTypeMapper;
import com.liuya.safe.client.dao.SafeUserTypeMapper;
import com.liuya.safe.client.pojo.AppUserType;
import com.liuya.safe.client.pojo.Application;
import com.liuya.safe.client.pojo.UserMetadata;
import com.liuya.safe.client.pojo.UserType;
import com.liuya.safe.client.service.UserTypeManager;
import com.liuya.safe.exception.AuthException;
import com.liuya.safe.util.Factory;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.liuya.db.DBLevelException;
import com.liuya.db.DBPower;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * @author liuya
 * @date 20161013
 */
@Service("userTypeManager")
public class UserTypeManagerImpl implements UserTypeManager {
    private static Log logger = LogFactory.getLog(UserTypeManagerImpl.class);

    @Autowired
    AppUserTypeMapper appUserTypeMapper;
    @Autowired
    @Qualifier("safeUserTypeMapper")
    SafeUserTypeMapper userTypeMapper;

    /**
     * UserType cache store
     */
    Map userTypeMap = new HashMap();
    private boolean changed = true;

    public UserTypeManagerImpl() {
    }

    private synchronized void loadIntoMemory() {
		if (!changed) {
			return;
		}
        Collection allUserTypes = userTypeMapper.selectAll();;

        userTypeMap.clear();
        for (Iterator iter = allUserTypes.iterator(); iter.hasNext();) {
            UserType userType = (UserType) iter.next();
            extractUserMetadata(userType);
            userTypeMap.put(userType.getName(), userType);
        }

        changed = false;
    }

    private void preModify(UserType userType) {
        String xml = getUserMetadataXML(userType);
        if (xml.length() >= 1000 && xml.length() <= 2000) {
            xml = StringUtils.rightPad(xml, 4000 - xml.length());
        }

        userType.setUserMetadataXML(xml);
    }

    public void addUserType(UserType userType) throws EntityExistException {
        loadIntoMemory();

        // check duplicated name
        if (userTypeMap.keySet().contains(userType.getName())) {
            throw new EntityExistException("The name '" + userType.getName() + "' exists already.");
        }

        preModify(userType);

        extractUserMetadata(userType);
        userTypeMapper.insert(userType);

        changed = true;
    }

    private String getUserMetadataXML(UserType userType) {
        int ch = 0;
        StringBuffer buf = new StringBuffer();
        try {
            FileReader reader = new FileReader(userType.getSrcFile());
            while ((ch = reader.read()) != -1) {
                buf.append((char) ch);
            }
            reader.close();
            return buf.toString();
        } catch (Exception e) {
            throw new AuthException(e);
        }
    }

    public void deleteUserType(String name) {
        loadIntoMemory();

        Collection applications = getApplications(name);

        Connection conn = null;
        boolean autoCommit = true;
        try {
            conn = DBPower.getConnectionByName(null);
            autoCommit = conn.getAutoCommit();
            conn.setAutoCommit(false);

            // drop application's user-role tables
            for (Iterator itr = applications.iterator(); itr.hasNext();) {
                Application application = (Application) itr.next();
                String dropSql = DBUtil.userRoleTableDropSql(application.getName(), name);
                try {
                    DBUtil.exec(conn, dropSql);
                } catch (SQLException e) {
                    logger.error("Failed to delete table: " + dropSql);
                }
            }

            // delete app-usertype relationship
            AppUserType appUserTypeHint = new AppUserType();
            appUserTypeHint.setUserTypeName(name);
            appUserTypeMapper.deleteByName(appUserTypeHint);

            // delete UserType
            UserType userTypeHint = new UserType();
            userTypeHint.setName(name);
            userTypeMapper.deleteByName(userTypeHint);

            conn.commit();
        } catch (SQLException e) {
            DBUtil.rollback(conn);
            throw new DBLevelException(e);
        } finally {
            DBUtil.setCommitMode(conn, autoCommit);
            DBUtil.close(conn);
        }

        // userTypeMap.remove(name);
        changed = true;
        // notify Factory
        Factory.userTypeChanged(name);
    }

    public Collection getAllUserTypes() {
        loadIntoMemory();

        return userTypeMap.values();
    }

    public UserType getUserType(String name) {
        loadIntoMemory();

        return (UserType) userTypeMap.get(name);
    }

    /**
     * Extract UserMetadata from userType's xml definition, and validate the
     * infomation
     *
     * @param userType
     */
    private void extractUserMetadata(UserType userType) {
        // parse
        if (userType.getUserMetadataXML() != null) {
            UserMetadataParser parser = new UserMetadataParser();
            UserMetadata userMetadata = parser.parse(new ByteArrayInputStream(userType.getUserMetadataXML().getBytes()));

            String validInfo = userMetadata.getValidInfo();
            if (!StringUtil.isEmpty(validInfo)) {
                System.out.println("Error: user metadata is invalid! name=" + userType.getName());
                System.out.println(validInfo);
                System.out.println("userMetadataStr=");
                System.out.println(userType.getUserMetadataXML());
                throw new AuthException("User metadata is invalid! " + validInfo);
            }

            userType.setUserMetadata(userMetadata);
        }
    }

    public void updateUserType(UserType userType) {
        preModify(userType);
        extractUserMetadata(userType);
        try {
            userTypeMapper.updateByName(userType);
        } catch (Exception e) {
            throw new DBLevelException(e);
        }

        changed = true;

        // notify Factory
        Factory.userTypeChanged(userType.getName());
    }

    public Collection getApplications(String userTypeName) {
        loadIntoMemory();

        Set applications = new HashSet();

        Iterator appItr = Factory.getApplicationManager().getAllApplications().iterator();
        while (appItr.hasNext()) {
            Application application = (Application) appItr.next();
            Iterator itr = application.getUserTypes().iterator();
            while (itr.hasNext()) {
                UserType userType = (UserType) itr.next();
                if (userType.getName().equals(userTypeName))
                    applications.add(application);
            }
        }
        return applications;
    }

    public UserType getUserTypeCopy(String name) {
        loadIntoMemory();

        UserType cacheOne = (UserType) userTypeMap.get(name);
        if (cacheOne == null) {
            return null;
        } else {
            UserType copy = new UserType();
            copy.setDesc(cacheOne.getDesc());
            copy.setName(cacheOne.getName());
            copy.setSrcFile(cacheOne.getSrcFile());
            copy.setUserMetadataXML(cacheOne.getUserMetadataXML());
            extractUserMetadata(copy);

            return copy;
        }
    }

}
